import json
import logging
from urllib.parse import urlencode, parse_qs
from urllib.request import urlopen

from django.conf import settings
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData

from oauth import constants

logger = logging.getLogger('django')

class OAuthQQ(object):
    """
    QQ认证辅助工具类
    """
    def __init__(self, client_id=None, client_secret=None, redirect_uri=None,state=None):
        # 这里加载配置文件中的第三方(QQ)开发信息,是通过django配置而不是直接加载本地dev.py配置
        self.client_id = client_id or settings.QQ_CLIENT_ID
        self.client_secret = client_secret or settings.QQ_CLIENT_SECRET
        self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI
        # 用于保存登录成功后的跳转页面路径
        self.state = state or settings.QQ_STATE

    def get_qq_login_url(self):
        """
        获取qq登录网址
        :return: url网址
        """
        params = {
            'response_type': 'code',
            'client_id': self.client_id,
            'redirect_uri': self.redirect_uri,
            'state': self.state,
            'scope': 'get_user_info'
        }
        # urllib.parse.urlencode(query) 将query字典转换成url路径中的查询字符串
        url = 'https://graph.qq.com/oauth2.0/authorize?' + urlencode(params)
        return url

    def get_access_token(self, code):
        """
        根据code获取access_token
        :param code: qq提供的code
        :return: access_token
        """
        params = {
            'grant_type': 'authorization_code',
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'code': code,
            'redirect_uri': self.redirect_uri
        }
        # 组织根据code可以获取access_token的url,其后通过该url可以获取到access_token
        url = 'https://graph.qq.com/oauth2.0/token?' + urlencode(params)
        # urllib.request.urlopen(url, data=None) 发送http请求,如果data为None,发送get请求,如果data不为None,发送post请求;返回response响应对象.可以通过read()读取响应体数据,需要注意读取出的响应体数据为bytes类型.注意: 接口调用有错误时,返回code和msg字段,以url参数的形式返回
        response = urlopen(url)
        response_data = response.read().decode()
        # urllib.parse.parse_qs(qs) 将qs查询字符串数据转换成python字典类型
        data = parse_qs(response_data)
        # qs查询字符串--------access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
        # 第一个是access_token的值,第二个是有效期/单位秒,第三个是授权自动续期步骤中需要提供的参数

        access_token = data.get('access_token', None)  # 这里返回的值是一个列表,其中就装了access_token字符串
        # 这里access_token不为空时access_token[0]一定不为空
        if not access_token:
            logger.error("code=%s msg=%s" % (data.get('code'), data.get('msg')))
            raise Exception('获取access_token失败')
        return access_token[0]

    def get_openid(self, access_token):
        """
        通过access_token获取用户的openid
        :param access_token: qq提供的access_token
        :return: openid
        """
        url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token
        response = urlopen(url)
        response_data = response.read().decode()
        try:
            data = json.loads(response_data[10:-4])
        except Exception as e:
            data = parse_qs(response_data)
            logger.error('code=%s msg=%s' % (data.get('code', data.get('msg'))))
            raise e
        openid = data.get('openid', None)
        return openid

    @staticmethod
    def generate_save_user_token(openid):
        """
        生成保存用户数据的token
        :param openid: 用户openid
        :return: token
        """
        serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
        data = {
            'openid': openid
        }
        token = serializer.dumps(data)
        return token.decode()

    @staticmethod
    def check_save_user_token(token):
        """
        检验保存用户数据的token
        :param token: token
        :return: openid or None
        """
        serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
        try:
            data = serializer.loads(token)
        except BadData:
            return None
        else:
            return data.get('openid')